﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Net.Security;
using Configuration;

namespace Core
{
    class Utils
    {
        //public Stopwatch executionTime { get; set; }
        //public Stopwatch executionRead { get; private set; }
        //public Stopwatch executionSend { get; private set; }

        public Utils()
        {
            //executionRead = new Stopwatch();
            //executionSend = new Stopwatch();
            //executionTime = new Stopwatch();
        }

        #region Send
        
        public void SendRequestPacket(Model.Elements.HttpRequestPacket packet, Stream ns)
        {
            try
            {
                //executionSend.Start();

                StringBuilder sb = new StringBuilder();
                sb.Append(string.Format("{0} {1} {2}\r\n", packet.method, packet.uri, packet.version));
                foreach (Model.Elements.HttpHeader header in packet.lstHeaders)
                {
                    sb.Append(string.Format("{0}: {1}\r\n", header.name, header.value));
                }


                sb.Append("\r\n");

                ns.Write(ASCIIEncoding.ASCII.GetBytes(sb.ToString()), 0, sb.Length);

                if (packet.data != null)
                    ns.Write(packet.data, 0, packet.data.Length);
            }
            catch
            {
            }
            finally
            {
                //executionSend.Reset();
            }
        }
        
        public void SendResponsePacket(Model.Elements.HttpResponsePacket packet, Stream ns)
        {
            try
            {
                StringBuilder sb = new StringBuilder();

                if (!string.IsNullOrEmpty(packet.message))
                    sb.Append(string.Format("{0} {1} {2}\r\n", packet.version, packet.errorCode, packet.message));
                else
                    sb.Append(string.Format("{0} {1}\r\n", packet.version, packet.errorCode));

                foreach (Model.Elements.HttpHeader header in packet.lstHeaders)
                    sb.Append(string.Format("{0}: {1}\r\n", header.name, header.value));
                sb.Append("\r\n");

                byte[] packetHeaders = ASCIIEncoding.ASCII.GetBytes(sb.ToString());
                byte[] packetData = packet.data;
                byte[] fullPacket = new byte[packetHeaders.Length + packetData.Length];
                System.Buffer.BlockCopy(packetHeaders, 0, fullPacket, 0, packetHeaders.Length);
                System.Buffer.BlockCopy(packetData, 0, fullPacket, packetHeaders.Length, packetData.Length);
                ns.Write(fullPacket, 0, fullPacket.Length);
            }
            catch
            {
                //Log.Log.LogThisConsole("Error: error while sending response. Socket closed?");
            }
            finally
            {
                ns.Close();
            }
        }
        
        #endregion

        #region Recv
       
        public byte[] ReadRequest(Stream ns)
        {
            List<byte> bytesPacket = new List<byte>();
            int bufferLength = 1024;
            byte[] buffer = new byte[bufferLength];
            do
            {
                int readedBytes = 0;
                readedBytes = ns.Read(buffer, 0, bufferLength);

                byte[] temporalBuffer = new byte[readedBytes];
                Array.Copy(buffer, temporalBuffer, readedBytes);
                bytesPacket.AddRange(temporalBuffer);
            } while (!Parsers.Request.IsValidPacket(bytesPacket.ToArray()));

            return bytesPacket.ToArray();
        }
        
        //public byte[] ReadResponse(Stream ns)
        //{
        //    //executionRead.Start();

        //    int readtimeout = 400;
        //    int defaultTimeout = ns.ReadTimeout;
        //    ns.ReadTimeout = readtimeout;

        //    int bufferLength = 1024;
        //    byte[] buffer = new byte[bufferLength];

        //    System.Collections.ArrayList bytesPacket = new System.Collections.ArrayList();

        //    bool finish = false;
        //    Stopwatch watcher = new Stopwatch();
        //    watcher.Start();
        //    do
        //    {
        //        int readedBytes = 0;
        //        try
        //        {

        //            readedBytes = ns.Read(buffer, 0, bufferLength);
        //            //executionRead.Restart();

        //            if (readedBytes <= 0)
        //                finish = true;
        //        }
        //        catch
        //        {
        //            if (bytesPacket.Count > 0 || watcher.ElapsedMilliseconds > 2000)
        //            {
        //                finish = true;
        //            }
        //        }
        //        if (!finish)
        //        {
        //            if (readedBytes <= 0)
        //                continue;

        //            byte[] newBuffer = new byte[readedBytes];
        //            Array.Copy(buffer, newBuffer, readedBytes);
        //            bytesPacket.Add(newBuffer);
        //        }
        //    } while (!finish);

        //    ns.ReadTimeout = defaultTimeout;

        //    //executionRead.Stop();

        //    byte[] finalBuffer = ReassembleBuffers(bytesPacket);
        //    return finalBuffer;
        //}
        
        
        public byte[] ReadResponse(Stream ns)
        {
            int bufferLength = 1024;
            byte[] buffer = new byte[bufferLength];

            List<byte> bytesPacket = new List<byte>();

            do
            {
                int readedBytes = 0;
                readedBytes = ns.Read(buffer, 0, bufferLength);
                byte[] temporalBuffer = new byte[readedBytes];
                Array.Copy(buffer, temporalBuffer, readedBytes);
                bytesPacket.AddRange(temporalBuffer);
            } while (!Parsers.Response.IsValidPacket(bytesPacket.ToArray()));

            return bytesPacket.ToArray();
        }
         
        #endregion

        #region Misc
        
        public Configuration.RelationDomainIp GetFordwardOfHost(string host)
        {
            foreach (RelationDomainIp relation in Configuration.Configuration.lstRelationDomainIp)
            {
                if (relation.domain == host)
                    return relation;
            }

            return null;
        }
        #endregion

        #region error response codes

        /// <summary>
        /// Critical error. It is throwed when error template doesn't exist or when an exception is not properly handled
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public Model.Elements.HttpResponsePacket Get500Error(string message)
        {
            Model.Elements.HttpResponsePacket response = null;
            if (Configuration.Configuration.debug)
                response = IPlugin.API.General.ResponseByErrorCode("500", "HTTP/1.0", "Critical error", message);
            else
                response = IPlugin.API.General.ResponseByErrorCode("500", "HTTP/1.0", "Critical error", "Critical error");

            if (response != null)
                return response;
            else
            {
                Model.Elements.HttpResponsePacket packet500 = new Model.Elements.HttpResponsePacket("HTTP/1.0", "500", "Critical error");
                packet500.AddHeader(new Model.Elements.HttpHeader("Content-Type", "text/html"));

                if (Configuration.Configuration.debug)
                    packet500.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Critical error</b>: <i>{0}</i><br/><br/>* Debug is enabled. If you don't want to see this message please disable it.</html>", message));
                else
                    packet500.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Critical error</b></html>"));
                return packet500;
            }
        }

        /// <summary>
        /// Bad request error
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public Model.Elements.HttpResponsePacket Get400Error(string message)
        {
            Model.Elements.HttpResponsePacket packet400 = new Model.Elements.HttpResponsePacket("HTTP/1.0", "400", "Bad request");
            packet400.AddHeader(new Model.Elements.HttpHeader("Content-Type", "text/html"));

            if (Configuration.Configuration.debug)
                packet400.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Bad request</b>: <i>{0}</i></html>", message));
            else
                packet400.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Bad request</b></html>"));
            return packet400;
        }

        /// <summary>
        /// Service unavaliable error
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public Model.Elements.HttpResponsePacket Get503Error(string message)
        {
            Model.Elements.HttpResponsePacket packet503 = new Model.Elements.HttpResponsePacket("HTTP/1.0", "503", "Service unavailable");
            packet503.AddHeader(new Model.Elements.HttpHeader("Content-Type", "text/html"));

            if (Configuration.Configuration.debug)
                packet503.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Service unavailable</b>: <i>{0}</i></html>", message));
            else
                packet503.data = ASCIIEncoding.ASCII.GetBytes(string.Format("<html><b>Service unavailable</b></html>"));
            return packet503;
        }
        #endregion
    }
}
